using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Iesi.Collections;
using Sedna.Core.Controls.Support;
using Sedna.Core.Persistence;
using Sedna.Util;
using Spring.Util;

namespace Sedna.Core.Controller
{
    public class MultiSelectListController : ListController, IMultiSelectListController
    {
        private const String SELECTED_ITEMS_KEY = "selectedItems";
        private const String LAST_SELECTED_ITEMS_KEY = "lastSelectedItems";
        private const String LAST_UNSELECTED_ITEMS_KEY = "lastUnselectedItems";             
        private const String SELECTED_VALUES_KEY = "selectedValues";

        private String selectedItemsPath;
        private String lastSelectedItemsPath;
        private String lastUnselectedItemsPath;

        # region Properties

        public string SelectedItemsPath
        {
            get { return selectedItemsPath; }
            set { selectedItemsPath = value; }
        }

        public string LastSelectedItemsPath
        {
            get { return lastSelectedItemsPath; }
            set { lastSelectedItemsPath = value; }
        }

        public string LastUnselectedItemsPath
        {
            get { return lastUnselectedItemsPath; }
            set { lastUnselectedItemsPath = value; }
        }
        
        /// <summary>
        /// Make a sense only if <see cref="SelectedItemsPath"/> is set
        /// otherwise always returns empty set
        /// </summary>
        public ISet SelectedItems
        {
            get
            {
                if (!StringUtils.IsNullOrEmpty(SelectedItemsPath))
                {
                    return GetSetByPath(SelectedItemsPath);
                }

                return  GetSetFromContext(SELECTED_ITEMS_KEY);
            }
        }

        /// <summary>
        /// Make a sense only if <see cref="LastSelectedItemsPath"/> is set
        /// otherwise always returns empty set
        /// </summary>
        public ISet LastSelectedItems
        {
            get
            {
                if (!StringUtils.IsNullOrEmpty(LastSelectedItemsPath))
                {
                    return GetSetByPath(LastSelectedItemsPath);
                }

                return GetSetFromContext(LAST_SELECTED_ITEMS_KEY);
            }
        }

        /// <summary>
        /// Make a sense only if <see cref="LastUnselectedItemsPath"/> is set
        /// otherwise always returns empty set
        /// </summary>
        public ISet LastUnselectedItems
        {
            get
            {
                if (!StringUtils.IsNullOrEmpty(LastUnselectedItemsPath))
                {
                    return GetSetByPath(LastUnselectedItemsPath);
                }

                return GetSetFromContext(LAST_UNSELECTED_ITEMS_KEY);
            }
        } 

        # endregion

        private ISet SelectedValues
        {
            get { return GetSetFromContext(SELECTED_VALUES_KEY); }
        }
        
        private bool IsSelectedValuesChanged
        {
            get
            {
                ISet currentSelectedKeys = new HashedSet(Control.SelectedValues as ICollection);

                ISet previousSelectedKeys = new HashedSet(SelectedValues);

                // Trying to avoid unnecessary and untimely data loading by cheking if a set of selected items has been changed
                return
                    (currentSelectedKeys.Count != SelectedValues.Count ||
                     previousSelectedKeys.RetainAll(currentSelectedKeys));                
            }
        }

        protected override void CustomBind(ICollection data)
        {
            int index = 0;
            
            Hashtable selectedItems = new Hashtable(ElementsComparer);
            
            foreach (object selectedItem in SelectedItems)
            {
                selectedItems.Add(selectedItem, null);
            }

            foreach (object obj in data)
            {
                ListItem item = Control.Items[index];

                ItemsCache.Add(item.Value, obj);

                if (!String.IsNullOrEmpty(SelectedItemsPath))
                {
                    if (selectedItems.Contains(obj))
                    {
                        item.Selected = true;
                    }                    
                }

                index++;
            }
        }

        protected override void Control_AfterBinding(Control control)
        {
            SelectedValues.Clear();
            
            if (!String.IsNullOrEmpty(SelectedItemsPath) ||
                !String.IsNullOrEmpty(LastSelectedItemsPath) ||
                !String.IsNullOrEmpty(LastUnselectedItemsPath))
            {                
                SelectedValues.AddAll(Control.SelectedValues as ICollection);
            }            
        }

        protected override void Control_AfterUnbinding(Control control)
        {
            if (!controlBinded)
                return;

            controlBinded = false;

            AttachCacheToSession();

            if (!(Control as Control).Page.IsPostBack)
                return;
            
            if (String.IsNullOrEmpty(SelectedItemsPath) &&
                String.IsNullOrEmpty(LastSelectedItemsPath) &&
                String.IsNullOrEmpty(LastUnselectedItemsPath))
            {
                return;
            }
            
            LastSelectedItems.Clear();
            LastUnselectedItems.Clear();

            if (!IsSelectedValuesChanged && String.IsNullOrEmpty(SelectedItemsPath))
                return;  // If data aren't cached we avoid unnecessary and untimely data loading

            // If selected values have changed LastSelectedItems and/or LastUnselectedItems should be set
            
            // Even if selected values have not changed SelectedItems should be set

            if (!CacheData) // no caching
            {                                
                ISet currentSelectedKeys = new HashedSet(Control.SelectedValues as ICollection);                

                // This data loading is untimely but necessary to get the objects by selected values
                lastUsedDataProvider = (NewDataProvider != null) ? NewDataProvider : CurrentDataProvider;

                ICollection data = new ArrayList(lastUsedDataProvider.LoadData());

                Control.DataSource = data;
                Control.DataBind();

                int index = 0;

                foreach (object obj in data)
                {
                    ListItem item = Control.Items[index];

                    item.Selected = currentSelectedKeys.Contains(item.Value);

                    ItemsCache.Add(item.Value, obj);

                    index++;
                }
            }

                        
            // trying to aviod unnecessary rewriting
            // it is useful if you use EqualsComparer and don't want 
            // SelectedItem to be rewritten if they are equal by Equals() method
            // but not equal by references

            // SelectedItems.Clear();

            Hashtable prevSelectedItems = new Hashtable(ElementsComparer);            

            if (!String.IsNullOrEmpty(SelectedItemsPath))
            {
                foreach (object selectedItem in SelectedItems)
                {
                    prevSelectedItems.Add(selectedItem, selectedItem);
                }
            }   
            
            HashedSet currSelectedItems = new HashedSet();
            
            // at current moment SelectedValues contains previously selected values

            foreach (ListItem item in Control.Items)
            {
                if (!item.Selected) continue;
                
                // Was selected last time?
                if (SelectedValues.Remove(item.Value))
                {
                    // No...

                    object obj = prevSelectedItems[ItemsCache[item.Value]];
                    
                    if (obj == null)
                    {
                        throw new Exception(
                            String.Format("Previously selected entity with key='{0}' not found", item.Value));
                    }
                    
                    currSelectedItems.Add(obj);
                    
                    continue;
                }
                
                // Yes!

                currSelectedItems.Add(ItemsCache[item.Value]);                

                if (!String.IsNullOrEmpty(LastSelectedItemsPath))
                {
                    LastSelectedItems.Add(ItemsCache[item.Value]);
                }
            }

            if (!String.IsNullOrEmpty(SelectedItemsPath))
            {
                SelectedItems.Clear();
                SelectedItems.AddAll(currSelectedItems);
            }                       

            // at current moment SelectedValues contains values
            // that were unselected last time

            if (!String.IsNullOrEmpty(LastUnselectedItemsPath))
            {
                foreach (String value in SelectedValues)
                {
                    LastUnselectedItems.Add(ItemsCache[value]);
                }
            }                        
            
            SelectedValues.Clear();
        }
    }
}
